home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C++ / Applications / Nuntius 1.2 / src / Nuntius / UThread.cp < prev    next >
Encoding:
Text File  |  1994-03-16  |  16.0 KB  |  645 lines  |  [TEXT/MPS ]

  1. // Copyright © 1992 Peter Speck, speck@dat.ruc.dk. All rights reserved.
  2. // UThread.cp
  3.  
  4. #include "UThread.h"
  5. #include "UThreadBuiltIn.h"
  6. #include "UThreadLongJmp.h"
  7. #include "UThreadApple.h"
  8. #include "UFatalError.h"
  9. #include "UProgress.h"
  10. #include "UProgressCache.h"
  11.  
  12. #include <RsrcGlobals.h>
  13.  
  14. #ifndef __STDIO__
  15. #include <stdio.h>
  16. #endif
  17.  
  18. #pragma segment MyThread
  19.  
  20. #define qDebugThreadsCreate qDebug & 0
  21. #define qDebugYieldDisable qDebug & 0
  22. #define qDebugYieldDisableVerbose qDebugYieldDisable & 0
  23.  
  24. Boolean gUsesThreadApple = false;
  25. Boolean gUsesThreadLongJmp = false;
  26.  
  27. TThread *gHeadThread = nil;
  28. TThread *gCurThread = nil;
  29. TProgress *gCurProgress = nil;
  30. TList *gAllThreads = nil;
  31. TList *gAbortedThreads = nil;
  32. TList *gScheduleBackThreads = nil;
  33. TList *gThreadListList = nil;
  34. Boolean gThreadUnitInited = false;
  35. ArrayIndex gLastScheduleIndex = 1;
  36. TThreadList *gApplWideThreads = nil;
  37.  
  38. //=======================================================================================
  39. TThread *PreferedThreadToRunNow() // nil if no prefered
  40. {
  41.     TThread *thread = nil;
  42.     if (gAbortedThreads->fSize)
  43.     {
  44.         thread = (TThread*)gAbortedThreads->Last();
  45. #if qDebugSchedule
  46.         fprintf(stderr, "MySchedule: Has %ld threads to abort/kill, returns TThread at $%lx\n", gAbortedThreads->GetSize(), long(thread));
  47. #endif
  48.     }
  49.     else if (gScheduleBackThreads->fSize)
  50.     {
  51.         thread = (TThread*)gScheduleBackThreads->Pop();
  52. #if qDebugSchedule
  53.         fprintf(stderr, "MySchedule: Had %ld threads to schedule back to, returns TThread at $%lx\n", gScheduleBackThreads->GetSize() + 1, long(thread));
  54. #endif
  55.     }
  56.     else
  57.         return nil;
  58. #if qDebug
  59.     FailNonObject(thread);
  60. #endif
  61.     return thread;
  62. }
  63.  
  64. TThread *FindOrdinaryThreadToRun()
  65. {
  66.     while (true)
  67.     {
  68.         if (++gLastScheduleIndex > gAllThreads->fSize)
  69.             gLastScheduleIndex = 1;
  70.         TThread *thread = (TThread*)gAllThreads->At(gLastScheduleIndex);
  71.         if (thread->IsStarted() == false)
  72.             continue;
  73.         return thread;
  74.     }
  75. }
  76.  
  77. TThread *MySchedule()
  78. {
  79.     TThread *thread = PreferedThreadToRunNow();
  80.     if (thread == nil)
  81.     {
  82.         if (MemSpaceIsLow())
  83.         {
  84. #if qDebug
  85.             fprintf(stderr, "MySchedule: MemSpaceIsLow, so we swap to main thread\n");
  86. #endif
  87.             if (gLastScheduleIndex > 1) // if we ran lowspace in non-main thread, warn user now
  88.             {
  89.                 InvalidateMenus();
  90.                 gApplication->SpaceIsLowAlert();
  91.             }
  92.             gLastScheduleIndex = 1; // user can abort threads to free up memory
  93.         }
  94.         thread = FindOrdinaryThreadToRun();
  95. #if qDebugScheduleVerbose
  96.         fprintf(stderr, "MySchedule: Index = %ld, TThread at $%lx\n", gLastScheduleIndex, long(thread));
  97. #endif
  98.     }
  99. #if qDebug
  100.     FailNonObject(thread);
  101. #endif
  102.     return thread;
  103. }
  104. //=======================================================================================
  105. TThread::TThread()
  106. {
  107. }
  108.  
  109. pascal void TThread::Initialize()
  110. {
  111.     inherited::Initialize();
  112. #if qDebug
  113.     if (!gThreadUnitInited)
  114.         ProgramBreak("Fatal: InitUThread not called before creating a TThread");
  115. #endif
  116.     fProgress = nil;
  117.     fIsAborting = false;
  118.     fIsKilling = false;
  119.     fYieldIsDisabled = false;
  120.     fSavedTopHandler = nil; // no link back in stack
  121.     fLastYieldTick = 0;
  122.     fIsStarted = false;
  123. }
  124.  
  125. void TThread::IThread(Boolean isMainThread, const char *debugDoingWhat)
  126. {
  127.     inherited::IObject();
  128.     FailInfo fi;
  129.     if (fi.Try())
  130.     {
  131.         fIsMainThread = isMainThread;
  132.         fDebugDoingWhat = debugDoingWhat;
  133.         
  134.         fProgress = gProgressCache->GetProgress();
  135.         fProgress->SetThread(this);
  136.         fi.Success();
  137.     }
  138.     else // fail
  139.     {
  140.         FreeIfObject(this);
  141.         fi.ReSignal();
  142.     }
  143. #if qDebugThreadsCreate
  144.     CStr255 s;
  145.     GetClassName(s);
  146.     fprintf(stderr, "%s: TThread = $%lx, pgrss = $%lx,   %s\n", (char*)s, this, fProgress, debugDoingWhat);
  147. #endif
  148. }
  149.  
  150. pascal void TThread::Free()
  151. {
  152. #if qDebug
  153.     if (!VerboseIsObject(this))
  154.         ProgramBreak("Got a bad TThread!");
  155. #endif
  156. #if qDebugThreadsCreate
  157.     fprintf(stderr, "FreeThread: TThread = $%lx, pgrss = $%lx,    %s\n", long(this), long(fProgress), fDebugDoingWhat);
  158. #endif
  159.     for (ArrayIndex index = 1; index <= gThreadListList->fSize; ++index)
  160.     {
  161.         TThreadList *tl = (TThreadList *)gThreadListList->At(index);
  162. #if qDebug
  163.         if (!VerboseIsObject(tl))
  164.             ProgramBreak("Got a bad TThreadList!");
  165. #endif
  166.         tl->Delete(this);
  167.     }
  168.     gAllThreads->Delete(this);
  169.     gAbortedThreads->Delete(this);
  170.     gScheduleBackThreads->Delete(this);
  171.     if (gAllThreads->GetEqualItemNo(this))
  172.         PanicExitToShell("Twice in gAllThreads");
  173.     if (gAbortedThreads->GetEqualItemNo(this))
  174.         PanicExitToShell("Twice in gAbortedThreads");
  175.     if (gScheduleBackThreads->GetEqualItemNo(this))
  176.         PanicExitToShell("Twice in gScheduleBackThreads");
  177. #if qDebug
  178.     if (fProgress && !VerboseIsObject(fProgress))
  179.         ProgramBreak("Got a bad fProgress!");
  180. #endif
  181.     if (fProgress)
  182.         fProgress->WorkDone();
  183.     gProgressCache->ReturnProgress(fProgress); fProgress = nil;
  184.  
  185.     Boolean isStarted = fIsStarted;
  186.     inherited::Free();
  187.     if (!isStarted)
  188.         return; // is not in this thread, so don't overwrite current thread with another one
  189.     // 'this' is now invalid, nonexisting
  190.     gCurThread = nil;
  191. }
  192.  
  193. void TThread::Die()
  194. {
  195.     if (fIsStarted)
  196.     {
  197.         while (gScheduleBackThreads->GetEqualItemNo(this) != kEmptyIndex)
  198.         {
  199. #if qDebug
  200.             fprintf(stderr, "TThread::Free, this thread = $%lx is in gScheduleBackThreads\n", long(this));
  201. #endif
  202.             gScheduleBackThreads->Delete(this);
  203.         }
  204.         if (this == gHeadThread)
  205.             PanicExitToShell("TThread::Free: Tried to Free() main thread (A mac without cpu? No, that wouldn't be nice)");
  206.         if (gCurThread != this)
  207.             PanicExitToShell("TThread::Free: 'this' is not gCurThread (I'm not me. Who? Me, not you!)");
  208.     }
  209.     //delete this;
  210.     FreeIfObject(this);
  211. }
  212. //=========================================================================
  213. void TThread::SwapThread()
  214. {
  215.     PanicExitToShell("TThread::SwapThread called");
  216. }
  217.  
  218. //=========================================================================
  219. TProgress *TThread::GetProgress()
  220. {
  221.     return fProgress;
  222. }
  223.  
  224. void TThread::CheckYield()
  225. {
  226.     if (TickCount() - fLastYieldTick <= 4)
  227.         return;
  228.     YieldTime();
  229. }
  230.  
  231. void TThread::YieldTime()
  232. {
  233.     if (this != gCurThread)
  234.     {
  235. #if qDebug
  236.         ProgramBreak("TThread::YieldTime called for non current thread");
  237. #endif
  238.         return;
  239.     }
  240.     if (gAllThreads->fSize == 1)
  241.     {
  242.         gApplication->UpdateAllWindows();
  243.         fLastYieldTick = TickCount();
  244.         return;
  245.     }
  246.     if (fIsAborting)
  247.         DoAbortThread();
  248.     if (fYieldIsDisabled)
  249.     {
  250. #if qDebugSchedule | qDebugYieldDisable
  251.         fprintf(stderr, "Thread at $%lx avoided yield as fYieldIsDisabled == true\n");
  252. #endif
  253.         return;
  254.     }
  255. #if qDebugScheduleVerbose
  256.     static char prevTxt[100];
  257.     sprintf(prevTxt, "< Thread at $%lx\n", long(this));
  258. #endif
  259.     SwapThread();
  260.     if (fIsAborting)
  261.         DoAbortThread();
  262. }
  263.  
  264. void TThread::Abort()
  265. {
  266. #if qDebug
  267.     fprintf(stderr, "Aborts thread at $%lx, abort flag %hd -> 1\n", long(this), fIsAborting);
  268. #endif
  269.     fIsAborting = true;
  270.     if (gAbortedThreads->GetEqualItemNo(this) == kEmptyIndex)
  271.         gAbortedThreads->InsertLast(this);
  272. }
  273.  
  274. void TThread::Kill()
  275. {
  276. #if qDebug
  277.     fprintf(stderr, "Kills thread at $%lx, kill flag %hd -> 1, fYieldIsDisabled = %hd\n", long(this), fIsAborting, fYieldIsDisabled);
  278. #endif
  279.     TThread *oldCurThread = gCurThread;
  280.     gScheduleBackThreads->Push(gCurThread);
  281.     fIsKilling = true;
  282.     fIsAborting = true;
  283.     if (gAbortedThreads->GetEqualItemNo(this) == kEmptyIndex)
  284.         gAbortedThreads->InsertLast(this);
  285.     // go kill it
  286.     Boolean oldDisable = gCurThread->SetYieldDisable(false);
  287.     gCurThread->YieldTime();                                 // if kills this thread, doesn't return
  288.     gCurThread->SetYieldDisable(oldDisable); // so no problem with this
  289.     if (gCurThread != oldCurThread)
  290.         PanicExitToShell("TThread::Kill: gCurThread is not oldCurThread");
  291.     // 'this' refers to an unexisting object. Lets see if that's true
  292.     // can't use GetEqualItemNo as it qDebug checks the object
  293.     for (ArrayIndex index = 1; index <= gAllThreads->fSize; ++index)
  294.         if (gAllThreads->TList::At(index) == this)
  295.             PanicExitToShell("TThread::Kill, killed thread is still alive (call Ghostbuster(sp?))");
  296. }
  297.  
  298. Boolean TThread::SetYieldDisable(Boolean disable)
  299. {
  300.     Boolean oldState = fYieldIsDisabled;
  301.     if (oldState != disable)
  302.     {
  303. #if qDebugYieldDisableVerbose
  304.         fprintf(stderr, "TThread::SetYieldDisable at $%lx, flag %ld -> %ld\n", long(this), long(oldState), long(disable));
  305. #endif
  306.         fYieldIsDisabled = disable;
  307.     }
  308.     return oldState;
  309. }
  310.  
  311. void TThread::DoAbortThread()
  312. {
  313. #if qDebug
  314.     if (!fIsAborting)
  315.     {
  316.         fprintf(stderr, "DoAbortThread: fIsAborting == %hd, fIsKilling = %hd\n", fIsAborting, fIsKilling);
  317.         ProgramBreak(gEmptyString);
  318.     }
  319. #endif
  320. #if qDebug
  321.         fprintf(stderr, "DoAbortThread: thread at $%lx is being %s with Failure(0, 0)\n", long(this), fIsKilling ? "killed" : "aborted");
  322. #endif
  323.     if (!fIsKilling)
  324.     {
  325.         fIsAborting = false;
  326.         gAbortedThreads->Delete(this);
  327.     }
  328.     Failure(0, 0);
  329. }
  330.  
  331. Boolean TThread::IsAborted()
  332. {
  333.     return fIsAborting;
  334. }
  335.  
  336. Boolean TThread::IsKilled()
  337. {
  338.     return fIsKilling;
  339. }
  340.  
  341. Boolean TThread::IsStarted()
  342. {
  343.     return fIsStarted;
  344. }
  345.  
  346. void TThread::SaveState()
  347. {
  348.     fSavedTopHandler = gTopHandler;
  349. }
  350.  
  351. void TThread::RestoreState()
  352. {
  353. #if qDebug
  354.     if (!VerboseIsObject(this))
  355.         ProgramBreak("Got a bad TThread!");
  356. #endif
  357. // globals
  358.     gCurThread = this;
  359.     gCurProgress = fProgress;
  360. // state
  361.     gTopHandler = fSavedTopHandler;
  362. // updates
  363.     fLastYieldTick = TickCount();
  364. }
  365.  
  366. const char *TThread::PeekShortDescription()
  367. {
  368.     return fDebugDoingWhat;
  369. }
  370.  
  371. void TThread::DumpDebugDescription()
  372. {
  373.     CStr255 s;
  374.     GetClassName(s);
  375.     fprintf(stderr, " %s at $%lx: %s\n", (char*)s, long(this), fDebugDoingWhat);
  376.     if (gCurThread == this)
  377.         fprintf(stderr, "   is current thread\n");
  378.     fprintf(stderr, "       fIsAborting = %lx, fIsKilling = %lx, fYieldIsDisabled = %lx\n", long(fIsAborting), long(fIsKilling), long(fYieldIsDisabled));
  379.     fprintf(stderr, "       fSavedTopHandler = $%lx, fLastYieldTick = %ld, fProgress = $%lx\n", long(fSavedTopHandler), fLastYieldTick, long(fProgress));
  380. }
  381.  
  382. //================== T H R E A D   U T I L I T I E S ===================================
  383.  
  384. void InitUThread()
  385. {
  386.     macroDontDeadStrip(TThreadList); // or SetEltType could fail
  387.     gThreadListList = NewList();
  388.     gThreadListList->SetEltType("TThreadList");
  389.     gAllThreads = NewList();
  390.     gAllThreads->SetEltType("TThread");
  391.     gAbortedThreads = NewList();
  392.     gAbortedThreads->SetEltType("TThread");
  393.     gScheduleBackThreads = NewList();
  394.     gScheduleBackThreads->SetEltType("TThread");
  395.     gApplWideThreads = NewThreadList("Appl-wide/homeless threads");
  396.     gThreadUnitInited = true;
  397.     gUsesThreadApple = HasThreadApple();
  398.     if (gUsesThreadApple == false)
  399.         gUsesThreadLongJmp = HasThreadLongJmp();
  400. #if qDebug
  401.     if (gUsesThreadApple)
  402.         fprintf(stderr, "Uses Apples Thread Manager\n");
  403.     else if (gUsesThreadLongJmp)
  404.         fprintf(stderr, "Uses builtin ThreadLongJmp\n");
  405.     else
  406.         fprintf(stderr, "Uses builtin thread swapper\n");
  407. #endif
  408.     TThread *t;
  409.     if (gUsesThreadApple)
  410.         t = CreateMainThreadApple();
  411.     else if (gUsesThreadLongJmp)
  412.         t = CreateMainThreadLongJmp();
  413.     else
  414.         t = CreateMainThreadBuiltIn();
  415.     gCurThread = gHeadThread = t;
  416.     gCurProgress = gCurThread->fProgress;
  417.     gCurThread->SetYieldDisable(true);
  418. #if qDebugThreadsCreate
  419.     fprintf(stderr, "HeadThread is at $%lx\n", long(gHeadThread));
  420. #endif
  421. }
  422.  
  423. void CloseDownUThread()
  424. {
  425.     if (!gThreadUnitInited)
  426.         return;    
  427.     if (gAllThreads->GetSize() != 1)
  428.     {
  429. #if qDebug
  430.         fprintf(stderr, "NoThreads == %ld != 1", gAllThreads->GetSize());
  431. #endif
  432.         PanicExitToShell("In CloseDownUThread(): head thread is not the only thread in this world");
  433.     }
  434. #if qDebug
  435.     if (!VerboseIsObject(gHeadThread))
  436.         ProgramBreak("CloseDownUThread: gHeadThread is not object");
  437. #endif
  438.     if (gHeadThread && gCurThread && gCurThread != gHeadThread)
  439.         PanicExitToShell("In CloseDownUThread(): gCurThread != gHeadThread");
  440. }
  441.  
  442. short GetNumThreads()
  443. {
  444.     return gAllThreads->fSize;
  445. }
  446.  
  447. //==============================================================================
  448. pascal void TThreadList::Initialize()
  449. {
  450.     inherited::Initialize();
  451.     fDescription = nil;
  452. }
  453.  
  454. void TThreadList::IThreadList(const char *debugListDescription)
  455. {
  456.     inherited::IList();    
  457.     FailInfo fi;
  458.     if (fi.Try())
  459.     {
  460.         SetEltType("TThread");
  461.         fDescription = debugListDescription;
  462.         gThreadListList->InsertLast(this);
  463.         fi.Success();
  464.     }
  465.     else // fail
  466.     {
  467.         fi.ReSignal();
  468.     }
  469. }
  470.  
  471. pascal void TThreadList::Free()
  472. {
  473. #if qDebug
  474.     if (!VerboseIsObject(this))
  475.         ProgramBreak("I'm a really bad TThread");
  476. #endif
  477.     if (fSize)
  478.     {
  479.         DebugDump();
  480.         strcpy(gPanicBuffer, "In TThreadList::Free, list is not empty. ListType: ");
  481.         strcat(gPanicBuffer, fDescription ? fDescription : "<unknown>");
  482.         strcat(gPanicBuffer, "\n");
  483.         PanicExitToShell(gPanicBuffer);
  484.     }
  485.     gThreadListList->Delete(this);
  486.     inherited::Free();
  487. }
  488.  
  489. TThread *TThreadList::ThreadAt(ArrayIndex index)
  490. {
  491. #if qDebug
  492.     if (!VerboseIsObject(this))
  493.         ProgramBreak("I'm a really bad TThreadList");
  494. #endif
  495.     TThread *thread = (TThread *)At(index);
  496. #if qDebug
  497.     if (!VerboseIsObject(thread))
  498.         ProgramBreak("Got a bad TThread!");
  499. #endif
  500.     return thread;
  501. }
  502.  
  503. pascal void TThreadList::DeleteAll()
  504. {
  505. #if qDebug
  506.     if (!VerboseIsObject(this))
  507.         ProgramBreak("I'm a really bad TThreadList");
  508. #endif
  509.     if (qDebug)
  510.         ProgramBreak("I would prefer if you called KillAll()");
  511.     KillAll();
  512. }
  513.  
  514. void TThreadList::KillAll()
  515. {
  516. #if qDebug
  517.     if (!VerboseIsObject(this))
  518.         ProgramBreak("I'm a really bad TThreadList");
  519. #endif
  520.     while (fSize)
  521.     {
  522.         ArrayIndex size = fSize;
  523.         TThread *thread = ThreadAt(fSize);
  524.         thread->Kill();
  525.         if (gAllThreads->GetIdentityItemNo(thread) == kEmptyIndex && fSize == size)
  526.             PanicExitToShell("In TThreadList::KillAll, I killed a thread, but its ghost is still in my list");
  527.     }
  528. }
  529.  
  530. TThread *TThreadList::ExecuteCommand(TCommand *command, const char *debugDoingWhat)
  531. {
  532. #if qDebug
  533.     if (!VerboseIsObject(this))
  534.         ProgramBreak("I'm a really bad TThreadList");
  535. #endif
  536.     TThread *thread = nil;
  537.     VOLATILE(thread);
  538.     FailInfo fi;
  539.     if (fi.Try())
  540.     {
  541.         if (gUsesThreadApple)
  542.             thread = ExecuteInNewThreadApple(command, debugDoingWhat);
  543.         else if (gUsesThreadLongJmp)
  544.             thread = ExecuteInNewThreadLongJmp(command, debugDoingWhat);
  545.         else
  546.             thread = ExecuteInNewThreadBuiltIn(command, debugDoingWhat);
  547.         InsertLast(thread);
  548.         fi.Success();
  549.         return thread;
  550.     }
  551.     else // fail
  552.     {
  553.         if (thread)
  554.             Delete(thread);
  555.         FreeIfObject(thread); thread = nil;        
  556.         fi.ReSignal();
  557.     }
  558. }
  559.  
  560. void TThreadList::DebugDump()
  561. {
  562.     fprintf(stderr, "  Dump of TThreadList at $%lx: %s\n", long(this), fDescription);
  563.     if (fSize == 0)
  564.         fprintf(stderr, "      <empty>\n");
  565.     else
  566.     {
  567.         for (ArrayIndex index = 1; index <= fSize; index++)
  568.         {
  569.             TThread *thread = ThreadAt(index);
  570.             fprintf(stderr, "     %2ld: at $%lx: %s\n", index, long(thread), thread->PeekShortDescription());
  571.         }
  572.     }
  573. }
  574.  
  575. TThreadList *NewThreadList(const char *debugListDescription)
  576. {
  577.     TThreadList *tl = new TThreadList();
  578.     tl->IThreadList(debugListDescription);
  579.     return tl;
  580. }
  581. //==============================================================================
  582. void ThreadExecuteCommand(TCommand *command)
  583. {
  584.     if (gTopHandler)
  585.         PanicExitToShell("In ThreadExecuteCommand(): gTopHandler != nil");
  586.     FailInfo fi;
  587.     if (fi.Try())
  588.     {
  589. #if qDebugThreadCommands
  590.         fprintf(stderr, "Executing command at $%lx in thread at $%lx\n", long(command), long(gCurThread));
  591. #endif
  592.         if (command)
  593.             command->DoIt();
  594.         FreeIfObject(command); command = nil;
  595.         fi.Success();
  596.     }
  597.     else // fail
  598.     {
  599.         if (fi.error != noErr)
  600.             gApplication->ShowError(fi.error, fi.message);
  601.         FailInfo fi2;
  602.         if (fi2.Try())
  603.         {
  604.             FreeIfObject(command); command = nil;
  605.             fi2.Success();
  606.         }
  607.     }
  608.     if (gTopHandler)
  609.         PanicExitToShell("In ThreadExecuteCommand() post exec: gTopHandler != nil");
  610.     gCurThread->Die();// does not return...
  611.     PanicExitToShell("In ThreadExecuteCommand, returned from gCurThread->Die();");
  612. }
  613. //--------------------------------------------------------------------------------------
  614. void DumpOneThreadList(TList *list)
  615. {
  616.     for (ArrayIndex index = 1; index <= list->GetSize(); index++)
  617.     {
  618.         TThread *thread = (TThread*)list->At(index);
  619.         thread->DumpDebugDescription();
  620.     }
  621. }
  622.  
  623. void DumpDebugThreadDescription()
  624. {
  625.     if (gAllThreads == nil)
  626.     {
  627.         fprintf(stderr, "Thread Unit not Inited\n");
  628.         return;
  629.     }
  630.     fprintf(stderr, "Debug dump of list of threads:  Uses %s\n",
  631.         gUsesThreadApple ? "Apples Thread Manager" : "builtin thread swapper");
  632.     DumpOneThreadList(gAllThreads);
  633.     fprintf(stderr, "Aborted threads:\n");
  634.     DumpOneThreadList(gAbortedThreads);
  635.     fprintf(stderr, "gScheduleBackThreads:\n");
  636.     DumpOneThreadList(gScheduleBackThreads);
  637.     fprintf(stderr, "Dump of lists of lists of threads:\n");
  638.     for (ArrayIndex index = 1; index <= gThreadListList->GetSize(); index++)
  639.     {
  640.         TThreadList *tl = (TThreadList*)gThreadListList->At(index);
  641.         tl->DebugDump();
  642.     }
  643.     fprintf(stderr, "End of dump\n");
  644. }
  645.